home *** CD-ROM | disk | FTP | other *** search
/ Amiga Tools 2 / Amiga Tools 2.iso / tools / vim / src / search.c < prev    next >
C/C++ Source or Header  |  1995-03-09  |  31KB  |  1,376 lines

  1. /* vi:ts=4:sw=4
  2.  *
  3.  * VIM - Vi IMproved        by Bram Moolenaar
  4.  *
  5.  * Read the file "credits.txt" for a list of people who contributed.
  6.  * Read the file "uganda.txt" for copying and usage conditions.
  7.  */
  8. /*
  9.  * search.c: code for normal mode searching commands
  10.  */
  11.  
  12. #include "vim.h"
  13. #include "globals.h"
  14. #include "proto.h"
  15. #include "param.h"
  16. #include "ops.h"        /* for mincl */
  17.  
  18. /* modified Henry Spencer's regular expression routines */
  19. #include "regexp.h"
  20.  
  21. static int inmacro __ARGS((char_u *, char_u *));
  22. static int cls __ARGS((void));
  23.  
  24. static char_u *top_bot_msg = (char_u *)"search hit TOP, continuing at BOTTOM";
  25. static char_u *bot_top_msg = (char_u *)"search hit BOTTOM, continuing at TOP";
  26.  
  27. /*
  28.  * This file contains various searching-related routines. These fall into
  29.  * three groups:
  30.  * 1. string searches (for /, ?, n, and N)
  31.  * 2. character searches within a single line (for f, F, t, T, etc)
  32.  * 3. "other" kinds of searches like the '%' command, and 'word' searches.
  33.  */
  34.  
  35. /*
  36.  * String searches
  37.  *
  38.  * The string search functions are divided into two levels:
  39.  * lowest:    searchit(); called by dosearch() and edit().
  40.  * Highest: dosearch(); changes curwin->w_cursor, called by normal().
  41.  *
  42.  * The last search pattern is remembered for repeating the same search.
  43.  * This pattern is shared between the :g, :s, ? and / commands.
  44.  * This is in myregcomp().
  45.  *
  46.  * The actual string matching is done using a heavily modified version of
  47.  * Henry Spencer's regular expression library.
  48.  */
  49.  
  50. /*
  51.  * Two search patterns are remembered: One for the :substitute command and
  52.  * one for other searches. last_pattern points to the one that was
  53.  * used the last time.
  54.  */
  55. static char_u     *search_pattern = NULL;
  56. static char_u     *subst_pattern = NULL;
  57. static char_u     *last_pattern = NULL;
  58.  
  59. static int        want_start;                /* looking for start of line? */
  60.  
  61. /*
  62.  * translate search pattern for regcomp()
  63.  *
  64.  * sub_cmd == 0: save pat in search_pattern (normal search command)
  65.  * sub_cmd == 1: save pat in subst_pattern (:substitute command)
  66.  * sub_cmd == 2: save pat in both patterns (:global command)
  67.  * which_pat == 0: use previous search pattern if "pat" is NULL
  68.  * which_pat == 1: use previous sustitute pattern if "pat" is NULL
  69.  * which_pat == 2: use last used pattern if "pat" is NULL
  70.  * 
  71.  */
  72.     regexp *
  73. myregcomp(pat, sub_cmd, which_pat)
  74.     char_u    *pat;
  75.     int        sub_cmd;
  76.     int        which_pat;
  77. {
  78.     regexp *retval;
  79.  
  80.     if (pat == NULL || *pat == NUL)     /* use previous search pattern */
  81.     {
  82.         if (which_pat == 0)
  83.         {
  84.             if (search_pattern == NULL)
  85.             {
  86.                 emsg(e_noprevre);
  87.                 return (regexp *) NULL;
  88.             }
  89.             pat = search_pattern;
  90.         }
  91.         else if (which_pat == 1)
  92.         {
  93.             if (subst_pattern == NULL)
  94.             {
  95.                 emsg(e_nopresub);
  96.                 return (regexp *) NULL;
  97.             }
  98.             pat = subst_pattern;
  99.         }
  100.         else    /* which_pat == 2 */
  101.         {
  102.             if (last_pattern == NULL)
  103.             {
  104.                 emsg(e_noprevre);
  105.                 return (regexp *) NULL;
  106.             }
  107.             pat = last_pattern;
  108.         }
  109.     }
  110.  
  111.     /*
  112.      * save the currently used pattern in the appropriate place,
  113.      * unless the pattern should not be remembered
  114.      */
  115.     if (!keep_old_search_pattern)
  116.     {
  117.         if (sub_cmd == 0 || sub_cmd == 2)    /* search or global command */
  118.         {
  119.             if (search_pattern != pat)
  120.             {
  121.                 free(search_pattern);
  122.                 search_pattern = strsave(pat);
  123.                 last_pattern = search_pattern;
  124.                 reg_magic = p_magic;        /* Magic sticks with the r.e. */
  125.             }
  126.         }
  127.         if (sub_cmd == 1 || sub_cmd == 2)    /* substitute or global command */
  128.         {
  129.             if (subst_pattern != pat)
  130.             {
  131.                 free(subst_pattern);
  132.                 subst_pattern = strsave(pat);
  133.                 last_pattern = subst_pattern;
  134.                 reg_magic = p_magic;        /* Magic sticks with the r.e. */
  135.             }
  136.         }
  137.     }
  138.  
  139.     want_start = (*pat == '^');        /* looking for start of line? */
  140.     reg_ic = p_ic;                    /* tell the regexec routine how to search */
  141.     retval = regcomp(pat);
  142.     return retval;
  143. }
  144.  
  145. /*
  146.  * lowest level search function.
  147.  * Search for 'count'th occurrence of 'str' in direction 'dir'.
  148.  * Start at position 'pos' and return the found position in 'pos'.
  149.  * Return OK for success, FAIL for failure.
  150.  */
  151.     int
  152. searchit(pos, dir, str, count, end, message)
  153.     FPOS    *pos;
  154.     int     dir;
  155.     char_u    *str;
  156.     long    count;
  157.     int        end;
  158.     int        message;
  159. {
  160.     int                 found;
  161.     linenr_t            lnum = 0;            /* init to shut up gcc */
  162.     linenr_t            startlnum;
  163.     regexp                *prog;
  164.     register char_u        *s;
  165.     char_u                *ptr;
  166.     register int        i;
  167.     register char_u        *match, *matchend;
  168.     int                 loop;
  169.  
  170.     if ((prog = myregcomp(str, 0, 2)) == NULL)
  171.     {
  172.         if (message)
  173.             emsg(e_invstring);
  174.         return FAIL;
  175.     }
  176. /*
  177.  * find the string
  178.  */
  179.     found = 1;
  180.     while (count-- && found)    /* stop after count matches, or no more matches */
  181.     {
  182.         startlnum = pos->lnum;    /* remember start of search for detecting no match */
  183.         found = 0;                /* default: not found */
  184.  
  185.         i = pos->col + dir;     /* search starts one postition away */
  186.         lnum = pos->lnum;
  187.  
  188.         if (dir == BACKWARD && i < 0)
  189.             --lnum;
  190.  
  191.         for (loop = 0; loop != 2; ++loop)   /* do this twice if 'wrapscan' is set */
  192.         {
  193.             for ( ; lnum > 0 && lnum <= curbuf->b_ml.ml_line_count; lnum += dir, i = -1)
  194.             {
  195.                 s = ptr = ml_get(lnum);
  196.                 if (dir == FORWARD && i > 0)    /* first line for forward search */
  197.                 {
  198.                     if (want_start || STRLEN(s) <= (size_t)i)   /* match not possible */
  199.                         continue;
  200.                     s += i;
  201.                 }
  202.  
  203.                 if (regexec(prog, s, dir == BACKWARD || i <= 0))
  204.                 {                            /* match somewhere on line */
  205.                     match = prog->startp[0];
  206.                     matchend = prog->endp[0];
  207.                     if (dir == BACKWARD && !want_start)
  208.                     {
  209.                         /*
  210.                          * Now, if there are multiple matches on this line,
  211.                          * we have to get the last one. Or the last one before
  212.                          * the cursor, if we're on that line.
  213.                          */
  214.                         while (*match != NUL && regexec(prog, match + 1, (int)FALSE))
  215.                         {
  216.                             if ((i >= 0) && ((prog->startp[0] - s) > i))
  217.                                 break;
  218.                             match = prog->startp[0];
  219.                             matchend = prog->endp[0];
  220.                         }
  221.  
  222.                         if ((i >= 0) && ((match - s) > i))
  223.                             continue;
  224.                     }
  225.  
  226.                     pos->lnum = lnum;
  227.                     if (end)
  228.                         pos->col = (int) (matchend - ptr - 1);
  229.                     else
  230.                         pos->col = (int) (match - ptr);
  231.                     found = 1;
  232.                     break;
  233.                 }
  234.                 /* breakcheck is slow, do it only once in 16 lines */
  235.                 if ((lnum & 15) == 0)
  236.                     breakcheck();       /* stop if ctrl-C typed */
  237.                 if (got_int)
  238.                     break;
  239.  
  240.                 if (loop && lnum == startlnum)  /* if second loop stop where started */
  241.                     break;
  242.             }
  243.     /* stop the search if wrapscan isn't set, after an interrupt and after a match */
  244.             if (!p_ws || got_int || found)
  245.                 break;
  246.  
  247.             /*
  248.              * If 'wrapscan' is set we continue at the other end of the file.
  249.              * If 'terse' is not set, we give a message.
  250.              * This message is also remembered in keep_msg for when the screen
  251.              * is redrawn. The keep_msg is cleared whenever another message is
  252.              * written.
  253.              */
  254.             if (dir == BACKWARD)    /* start second loop at the other end */
  255.             {
  256.                 lnum = curbuf->b_ml.ml_line_count;
  257.                 if (!p_terse && message)
  258.                 {
  259.                     msg(top_bot_msg);
  260.                     keep_msg = top_bot_msg;
  261.                 }
  262.             }
  263.             else
  264.             {
  265.                 lnum = 1;
  266.                 if (!p_terse && message)
  267.                 {
  268.                     msg(bot_top_msg);
  269.                     keep_msg = bot_top_msg;
  270.                 }
  271.             }
  272.         }
  273.         if (got_int)
  274.             break;
  275.     }
  276.  
  277.     free(prog);
  278.  
  279.     if (!found)             /* did not find it */
  280.     {
  281.         if (got_int)
  282.             emsg(e_interr);
  283.         else if (message)
  284.         {
  285.             if (p_ws)
  286.                 emsg(e_patnotf);
  287.             else if (lnum == 0)
  288.                 EMSG("search hit TOP without match");
  289.             else
  290.                 EMSG("search hit BOTTOM without match");
  291.         }
  292.         return FAIL;
  293.     }
  294.  
  295.     return OK;
  296. }
  297.  
  298. /*
  299.  * Highest level string search function.
  300.  * Search for the 'count'th occurence of string 'str' in direction 'dirc'
  301.  *                    If 'dirc' is 0: use previous dir.
  302.  * If 'str' is 0 or 'str' is empty: use previous string.
  303.  *              If 'reverse' is TRUE: go in reverse of previous dir.
  304.  *                 If 'echo' is TRUE: echo the search command and handle options
  305.  *              If 'message' is TRUE: may give error message
  306.  *
  307.  * return 0 for failure, 1 for found, 2 for found and line offset added
  308.  */
  309.     int
  310. dosearch(dirc, str, reverse, count, echo, message)
  311.     int                dirc;
  312.     char_u           *str;
  313.     int                reverse;
  314.     long            count;
  315.     int                echo;
  316.     int                message;
  317. {
  318.     FPOS            pos;        /* position of the last match */
  319.     char_u            *searchstr;
  320.     static int        lastsdir = '/';    /* previous search direction */
  321.     static int        lastoffline;/* previous/current search has line offset */
  322.     static int        lastend;    /* previous/current search set cursor at end */
  323.     static long     lastoff;    /* previous/current line or char offset */
  324.     int                old_lastsdir;
  325.     int                old_lastoffline;
  326.     int                old_lastend;
  327.     long            old_lastoff;
  328.     int                ret;        /* Return value */
  329.     register char_u    *p;
  330.     register long    c;
  331.     char_u            *dircp = NULL;
  332.  
  333.     /*
  334.      * save the values for when keep_old_search_pattern is set
  335.      * (no if around this because gcc wants them initialized)
  336.      */
  337.     old_lastsdir = lastsdir;
  338.     old_lastoffline = lastoffline;
  339.     old_lastend = lastend;
  340.     old_lastoff = lastoff;
  341.  
  342.     if (dirc == 0)
  343.         dirc = lastsdir;
  344.     else
  345.         lastsdir = dirc;
  346.     if (reverse)
  347.     {
  348.         if (dirc == '/')
  349.             dirc = '?';
  350.         else
  351.             dirc = '/';
  352.     }
  353.     searchstr = str;
  354.                                     /* use previous string */
  355.     if (str == NULL || *str == NUL || *str == dirc)
  356.     {
  357.         if (search_pattern == NULL)
  358.         {
  359.             emsg(e_noprevre);
  360.             ret = 0;
  361.             goto end_dosearch;
  362.         }
  363.         searchstr = (char_u *)"";    /* will use search_pattern in myregcomp() */
  364.     }
  365.     if (str != NULL && *str != NUL)    /* look for (new) offset */
  366.     {
  367.         /*
  368.          * Find end of regular expression.
  369.          * If there is a matching '/' or '?', toss it.
  370.          */
  371.         p = skip_regexp(str, dirc);
  372.         if (*p == dirc)
  373.         {
  374.             dircp = p;        /* remember where we put the NUL */
  375.             *p++ = NUL;
  376.         }
  377.         lastoffline = FALSE;
  378.         lastend = FALSE;
  379.         lastoff = 0;
  380.         /*
  381.          * Check for a line offset or a character offset.
  382.          * for get_address (echo off) we don't check for a character offset,
  383.          * because it is meaningless and the 's' could be a substitute command.
  384.          */
  385.         if (*p == '+' || *p == '-' || isdigit(*p))
  386.             lastoffline = TRUE;
  387.         else if (echo && (*p == 'e' || *p == 's' || *p == 'b'))
  388.         {
  389.             if (*p == 'e')            /* end */
  390.                 lastend = TRUE;
  391.             ++p;
  392.         }
  393.         if (isdigit(*p) || *p == '+' || *p == '-')     /* got an offset */
  394.         {
  395.             if (isdigit(*p) || isdigit(*(p + 1)))
  396.                 lastoff = atol((char *)p);        /* 'nr' or '+nr' or '-nr' */
  397.             else if (*p == '-')            /* single '-' */
  398.                 lastoff = -1;
  399.             else                        /* single '+' */
  400.                 lastoff = 1;
  401.             ++p;
  402.             while (isdigit(*p))            /* skip number */
  403.                 ++p;
  404.         }
  405.         searchcmdlen = p - str;            /* compute lenght of search command
  406.                                                         for get_address() */
  407.     }
  408.  
  409.     if (echo)
  410.     {
  411.         msg_start();
  412.         msg_outchar(dirc);
  413.         msg_outtrans(*searchstr == NUL ? search_pattern : searchstr, -1);
  414.         if (lastoffline || lastend || lastoff)
  415.         {
  416.             msg_outchar(dirc);
  417.             if (lastend)
  418.                 msg_outchar('e');
  419.             else if (!lastoffline)
  420.                 msg_outchar('s');
  421.             if (lastoff < 0)
  422.             {
  423.                 msg_outchar('-');
  424.                 msg_outnum((long)-lastoff);
  425.             }
  426.             else if (lastoff > 0 || lastoffline)
  427.             {
  428.                 msg_outchar('+');
  429.                 msg_outnum((long)lastoff);
  430.             }
  431.         }
  432.         msg_ceol();
  433.         (void)msg_check();
  434.  
  435.         gotocmdline(FALSE, NUL);
  436.         flushbuf();
  437.     }
  438.  
  439.     pos = curwin->w_cursor;
  440.  
  441.     c = searchit(&pos, dirc == '/' ? FORWARD : BACKWARD, searchstr, count, lastend, message);
  442.     if (dircp)
  443.         *dircp = dirc;            /* put second '/' or '?' back for normal() */
  444.     if (c == FAIL)
  445.     {
  446.         ret = 0;
  447.         goto end_dosearch;
  448.     }
  449.     if (lastend)
  450.         mincl = TRUE;            /* 'e' includes last character */
  451.  
  452.     if (!lastoffline)           /* add the character offset to the column */
  453.     {
  454.         if (lastoff > 0)        /* offset to the right, check for end of line */
  455.         {
  456.             p = ml_get_pos(&pos) + 1;
  457.             c = lastoff;
  458.             while (c-- && *p++ != NUL)
  459.                 ++pos.col;
  460.         }
  461.         else                    /* offset to the left, check for start of line */
  462.         {
  463.             if ((c = pos.col + lastoff) < 0)
  464.                 c = 0;
  465.             pos.col = c;
  466.         }
  467.     }
  468.  
  469.     if (!tag_busy)
  470.         setpcmark();
  471.     curwin->w_cursor = pos;
  472.     curwin->w_set_curswant = TRUE;
  473.  
  474.     if (!lastoffline)
  475.     {
  476.         ret = 1;
  477.         goto end_dosearch;
  478.     }
  479.  
  480. /*
  481.  * add the offset to the line number.
  482.  */
  483.     c = curwin->w_cursor.lnum + lastoff;
  484.     if (c < 1)
  485.         curwin->w_cursor.lnum = 1;
  486.     else if (c > curbuf->b_ml.ml_line_count)
  487.         curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
  488.     else
  489.         curwin->w_cursor.lnum = c;
  490.     curwin->w_cursor.col = 0;
  491.  
  492.     ret = 2;
  493.  
  494. end_dosearch:
  495.     if (keep_old_search_pattern)
  496.     {
  497.         lastsdir = old_lastsdir;
  498.         lastoffline = old_lastoffline;
  499.         lastend = old_lastend;
  500.         lastoff = old_lastoff;
  501.     }
  502.     return ret;
  503. }
  504.  
  505.  
  506. /*
  507.  * Character Searches
  508.  */
  509.  
  510. /*
  511.  * searchc(c, dir, type, count)
  512.  *
  513.  * Search for character 'c', in direction 'dir'. If 'type' is 0, move to the
  514.  * position of the character, otherwise move to just before the char.
  515.  * Repeat this 'count' times.
  516.  */
  517.     int
  518. searchc(c, dir, type, count)
  519.     int             c;
  520.     register int    dir;
  521.     int             type;
  522.     long            count;
  523. {
  524.     static int         lastc = NUL;    /* last character searched for */
  525.     static int        lastcdir;        /* last direction of character search */
  526.     static int        lastctype;        /* last type of search ("find" or "to") */
  527.     register int    col;
  528.     char_u            *p;
  529.     int             len;
  530.  
  531.     if (c != NUL)       /* normal search: remember args for repeat */
  532.     {
  533.         lastc = c;
  534.         lastcdir = dir;
  535.         lastctype = type;
  536.     }
  537.     else                /* repeat previous search */
  538.     {
  539.         if (lastc == NUL)
  540.             return FALSE;
  541.         if (dir)        /* repeat in opposite direction */
  542.             dir = -lastcdir;
  543.         else
  544.             dir = lastcdir;
  545.     }
  546.  
  547.     p = ml_get(curwin->w_cursor.lnum);
  548.     col = curwin->w_cursor.col;
  549.     len = STRLEN(p);
  550.  
  551.     /*
  552.      * On 'to' searches, skip one to start with so we can repeat searches in
  553.      * the same direction and have it work right.
  554.      * REMOVED to get vi compatibility
  555.      * if (lastctype)
  556.      *    col += dir;
  557.      */
  558.  
  559.     while (count--)
  560.     {
  561.             for (;;)
  562.             {
  563.                 if ((col += dir) < 0 || col >= len)
  564.                     return FALSE;
  565.                 if (p[col] == lastc)
  566.                         break;
  567.             }
  568.     }
  569.     if (lastctype)
  570.         col -= dir;
  571.     curwin->w_cursor.col = col;
  572.     return TRUE;
  573. }
  574.  
  575. /*
  576.  * "Other" Searches
  577.  */
  578.  
  579. /*
  580.  * showmatch - move the cursor to the matching paren or brace
  581.  *
  582.  * Improvement over vi: Braces inside quotes are ignored.
  583.  */
  584.     FPOS           *
  585. showmatch(initc)
  586.     int        initc;
  587. {
  588.     static FPOS        pos;                /* current search position */
  589.     int                findc;                /* matching brace */
  590.     int                c;
  591.     int             count = 0;            /* cumulative number of braces */
  592.     int             idx = 0;            /* init for gcc */
  593.     static char_u     table[6] = {'(', ')', '[', ']', '{', '}'};
  594.     int             inquote = 0;        /* non-zero when inside quotes */
  595.     register char_u    *linep;                /* pointer to current line */
  596.     register char_u    *ptr;
  597.     int                do_quotes;            /* check for quotes in current line */
  598.     int                hash_dir = 0;        /* Direction searched for # things */
  599.     int                comment_dir = 0;    /* Direction searched for comments */
  600.  
  601.     pos = curwin->w_cursor;
  602.     linep = ml_get(pos.lnum); 
  603.  
  604.     /*
  605.      * if initc given, look in the table for the matching character
  606.      */
  607.     if (initc != NUL)
  608.     {
  609.         for (idx = 0; idx < 6; ++idx)
  610.             if (table[idx] == initc)
  611.             {
  612.                 initc = table[idx = idx ^ 1];
  613.                 break;
  614.             }
  615.         if (idx == 6)            /* invalid initc! */
  616.             return NULL;
  617.     }
  618.     /*
  619.      * no initc given, look under the cursor
  620.      */
  621.     else
  622.     {
  623.         if (linep[0] == '#' && pos.col == 0)
  624.             hash_dir = 1;
  625.  
  626.         /*
  627.          * Are we on a comment?
  628.          */
  629.         if (linep[pos.col] == '/')
  630.         {
  631.             if (linep[pos.col + 1] == '*')
  632.             {
  633.                 comment_dir = 1;
  634.                 idx = 0;
  635.             }
  636.             else if (pos.col > 0 && linep[pos.col - 1] == '*')
  637.             {
  638.                 comment_dir = -1;
  639.                 idx = 1;
  640.             }
  641.         }
  642.         if (linep[pos.col] == '*')
  643.         {
  644.             if (linep[pos.col + 1] == '/')
  645.             {
  646.                 comment_dir = -1;
  647.                 idx = 1;
  648.             }
  649.             else if (pos.col > 0 && linep[pos.col - 1] == '/')
  650.             {
  651.                 comment_dir = 1;
  652.                 idx = 0;
  653.             }
  654.         }
  655.  
  656.         /*
  657.          * If we are not on a comment or the # at the start of a line, then
  658.          * look for brace anywhere on this line after the cursor.
  659.          */
  660.         if (!hash_dir && !comment_dir)
  661.         {
  662.             /*
  663.              * find the brace under or after the cursor
  664.              */
  665.             linep = ml_get(pos.lnum); 
  666.             for (;;)
  667.             {
  668.                 initc = linep[pos.col];
  669.                 if (initc == NUL)
  670.                     break;
  671.  
  672.                 for (idx = 0; idx < 6; ++idx)
  673.                     if (table[idx] == initc)
  674.                         break;
  675.                 if (idx != 6)
  676.                     break;
  677.                 ++pos.col;
  678.             }
  679.             if (idx == 6)
  680.             {
  681.                 if (linep[0] == '#')
  682.                     hash_dir = 1;
  683.                 else
  684.                     return NULL;
  685.             }
  686.         }
  687.         if (hash_dir)
  688.         {
  689.             /*
  690.              * Look for matching #if, #else, #elif, or #endif
  691.              */
  692.             mtype = MLINE;        /* Linewise for this case only */
  693.             ptr = linep + 1;
  694.             while (*ptr == ' ' || *ptr == TAB)
  695.                 ptr++;
  696.             if (STRNCMP(ptr, "if", (size_t)2) == 0 || STRNCMP(ptr, "el", (size_t)2) == 0)
  697.                 hash_dir = 1;
  698.             else if (STRNCMP(ptr, "endif", (size_t)5) == 0)
  699.                 hash_dir = -1;
  700.             else
  701.                 return NULL;
  702.             pos.col = 0;
  703.             while (!got_int)
  704.             {
  705.                 if (hash_dir > 0)
  706.                 {
  707.                     if (pos.lnum == curbuf->b_ml.ml_line_count)
  708.                         break;
  709.                 }
  710.                 else if (pos.lnum == 1)
  711.                     break;
  712.                 pos.lnum += hash_dir;
  713.                 linep = ml_get(pos.lnum);
  714.                 if ((pos.lnum & 15) == 0)
  715.                     breakcheck();
  716.                 if (linep[0] != '#')
  717.                     continue;
  718.                 ptr = linep + 1;
  719.                 while (*ptr == ' ' || *ptr == TAB)
  720.                     ptr++;
  721.                 if (hash_dir > 0)
  722.                 {
  723.                     if (STRNCMP(ptr, "if", (size_t)2) == 0)
  724.                         count++;
  725.                     else if (STRNCMP(ptr, "el", (size_t)2) == 0)
  726.                     {
  727.                         if (count == 0)
  728.                             return &pos;
  729.                     }
  730.                     else if (STRNCMP(ptr, "endif", (size_t)5) == 0)
  731.                     {
  732.                         if (count == 0)
  733.                             return &pos;
  734.                         count--;
  735.                     }
  736.                 }
  737.                 else
  738.                 {
  739.                     if (STRNCMP(ptr, "if", (size_t)2) == 0)
  740.                     {
  741.                         if (count == 0)
  742.                             return &pos;
  743.                         count--;
  744.                     }
  745.                     else if (STRNCMP(ptr, "endif", (size_t)5) == 0)
  746.                         count++;
  747.                 }
  748.             }
  749.             return NULL;
  750.         }
  751.     }
  752.  
  753.     findc = table[idx ^ 1];        /* get matching brace */
  754.     idx &= 1;
  755.  
  756.     do_quotes = -1;
  757.     while (!got_int)
  758.     {
  759.         /*
  760.          * Go to the next position, forward or backward. We could use
  761.          * inc() and dec() here, but that is much slower
  762.          */
  763.         if (idx)                          /* backward search */
  764.         {
  765.             if (pos.col == 0)           /* at start of line, go to previous one */
  766.             {
  767.                 if (pos.lnum == 1)      /* start of file */
  768.                     break;
  769.                 --pos.lnum;
  770.                 linep = ml_get(pos.lnum);
  771.                 pos.col = STRLEN(linep);    /* put pos.col on trailing NUL */
  772.                 do_quotes = -1;
  773.                     /* we only do a breakcheck() once for every 16 lines */
  774.                 if ((pos.lnum & 15) == 0)
  775.                     breakcheck();
  776.             }
  777.             else
  778.                 --pos.col;
  779.         }
  780.         else                            /* forward search */
  781.         {
  782.             if (linep[pos.col] == NUL)  /* at end of line, go to next one */
  783.             {
  784.                 if (pos.lnum == curbuf->b_ml.ml_line_count) /* end of file */
  785.                     break;
  786.                 ++pos.lnum;
  787.                 linep = ml_get(pos.lnum);
  788.                 pos.col = 0;
  789.                 do_quotes = -1;
  790.                     /* we only do a breakcheck() once for every 16 lines */
  791.                 if ((pos.lnum & 15) == 0)
  792.                     breakcheck();
  793.             }
  794.             else
  795.                 ++pos.col;
  796.         }
  797.  
  798.         if (comment_dir)
  799.         {
  800.             /* Note: comments do not nest, and we ignore quotes in them */
  801.             if (linep[pos.col] != '/' ||
  802.                             (comment_dir == 1 && pos.col == 0) ||
  803.                             linep[pos.col - comment_dir] != '*')
  804.                 continue;
  805.             return &pos;
  806.         }
  807.  
  808.         if (do_quotes == -1)        /* count number of quotes in this line */
  809.         {
  810.             /*
  811.              * count the number of quotes in the line, skipping \" and '"'
  812.              */
  813.             for (ptr = linep; *ptr; ++ptr)
  814.                 if (*ptr == '"' && (ptr == linep || ptr[-1] != '\\') &&
  815.                             (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\''))
  816.                     ++do_quotes;
  817.             do_quotes &= 1;            /* result is 1 with even number of quotes */
  818.  
  819.             /*
  820.              * If we find an uneven count, check current line and previous
  821.              * one for a '\' at the end.
  822.              */
  823.             if (!do_quotes)
  824.             {
  825.                 inquote = FALSE;
  826.                 if (ptr[-1] == '\\')
  827.                 {
  828.                     do_quotes = 1;
  829.                     if (idx)                    /* backward search */
  830.                         inquote = TRUE;
  831.                 }
  832.                 if (pos.lnum > 1)
  833.                 {
  834.                     ptr = ml_get(pos.lnum - 1);
  835.                     if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\')
  836.                     {
  837.                         do_quotes = 1;
  838.                         if (!idx)                /* forward search */
  839.                             inquote = TRUE;
  840.                     }
  841.                 }
  842.             }
  843.         }
  844.  
  845.         /*
  846.          * Things inside quotes are ignored by setting 'inquote'.
  847.          * If we find a quote without a preceding '\' invert 'inquote'.
  848.          * At the end of a line not ending in '\' we reset 'inquote'.
  849.          *
  850.          * In lines with an uneven number of quotes (without preceding '\')
  851.          * we do not know which part to ignore. Therefore we only set
  852.          * inquote if the number of quotes in a line is even,
  853.          * unless this line or the previous one ends in a '\'.
  854.          * Complicated, isn't it?
  855.          */
  856.         switch (c = linep[pos.col])
  857.         {
  858.         case NUL:
  859.             inquote = FALSE;
  860.             break;
  861.  
  862.         case '"':
  863.                 /* a quote that is preceded with a backslash is ignored */
  864.             if (do_quotes && (pos.col == 0 || linep[pos.col - 1] != '\\'))
  865.                 inquote = !inquote;
  866.             break;
  867.  
  868.         /*
  869.          * Skip things in single quotes: 'x' or '\x'.
  870.          * Be careful for single single quotes, eg jon's.
  871.          * Things like '\233' or '\x3f' are not skipped, there is never a
  872.          * brace in them.
  873.          */
  874.         case '\'':
  875.             if (idx)                        /* backward search */
  876.             {
  877.                 if (pos.col > 1)
  878.                 {
  879.                     if (linep[pos.col - 2] == '\'')
  880.                         pos.col -= 2;
  881.                     else if (linep[pos.col - 2] == '\\' && pos.col > 2 && linep[pos.col - 3] == '\'')
  882.                         pos.col -= 3;
  883.                 }
  884.             }
  885.             else if (linep[pos.col + 1])    /* forward search */
  886.             {
  887.                 if (linep[pos.col + 1] == '\\' && linep[pos.col + 2] && linep[pos.col + 3] == '\'')
  888.                     pos.col += 3;
  889.                 else if (linep[pos.col + 2] == '\'')
  890.                     pos.col += 2;
  891.             }
  892.             break;
  893.  
  894.         default:
  895.             if (!inquote)      /* only check for match outside of quotes */
  896.             {
  897.                 if (c == initc)
  898.                     count++;
  899.                 else if (c == findc)
  900.                 {
  901.                     if (count == 0)
  902.                         return &pos;
  903.                     count--;
  904.                 }
  905.             }
  906.         }
  907.     }
  908.     return (FPOS *) NULL;       /* never found it */
  909. }
  910.  
  911. /*
  912.  * findfunc(dir, what) - Find the next line starting with 'what' in direction 'dir'
  913.  *
  914.  * Return TRUE if a line was found.
  915.  */
  916.     int
  917. findfunc(dir, what, count)
  918.     int         dir;
  919.     int            what;
  920.     long        count;
  921. {
  922.     linenr_t    curr;
  923.  
  924.     curr = curwin->w_cursor.lnum;
  925.  
  926.     for (;;)
  927.     {
  928.         if (dir == FORWARD)
  929.         {
  930.                 if (curr++ == curbuf->b_ml.ml_line_count)
  931.                         break;
  932.         }
  933.         else
  934.         {
  935.                 if (curr-- == 1)
  936.                         break;
  937.         }
  938.  
  939.         if (*ml_get(curr) == what)
  940.         {
  941.             if (--count > 0)
  942.                 continue;
  943.             setpcmark();
  944.             curwin->w_cursor.lnum = curr;
  945.             curwin->w_cursor.col = 0;
  946.             return TRUE;
  947.         }
  948.     }
  949.  
  950.     return FALSE;
  951. }
  952.  
  953. /*
  954.  * findsent(dir, count) - Find the start of the next sentence in direction 'dir'
  955.  * Sentences are supposed to end in ".", "!" or "?" followed by white space or
  956.  * a line break. Also stop at an empty line.
  957.  * Return TRUE if the next sentence was found.
  958.  */
  959.     int
  960. findsent(dir, count)
  961.         int     dir;
  962.         long    count;
  963. {
  964.     FPOS            pos, tpos;
  965.     register int    c;
  966.     int             (*func) __PARMS((FPOS *));
  967.     int             startlnum;
  968.     int                noskip = FALSE;            /* do not skip blanks */
  969.  
  970.     pos = curwin->w_cursor;
  971.     if (dir == FORWARD)
  972.         func = incl;
  973.     else
  974.         func = decl;
  975.  
  976.     while (count--)
  977.     {
  978.         /* if on an empty line, skip upto a non-empty line */
  979.         if (gchar(&pos) == NUL)
  980.         {
  981.             do
  982.                 if ((*func)(&pos) == -1)
  983.                     break;
  984.             while (gchar(&pos) == NUL);
  985.             if (dir == FORWARD)
  986.                 goto found;
  987.         }
  988.         /* if on the start of a paragraph or a section and searching
  989.          * forward, go to the next line */
  990.         else if (dir == FORWARD && pos.col == 0 && startPS(pos.lnum, NUL, FALSE))
  991.         {
  992.             if (pos.lnum == curbuf->b_ml.ml_line_count)
  993.                 return FALSE;
  994.             ++pos.lnum;
  995.             goto found;
  996.         }
  997.         else if (dir == BACKWARD)
  998.             decl(&pos);
  999.  
  1000.         /* go back to the previous non-blank char */
  1001.         while ((c = gchar(&pos)) == ' ' || c == '\t' ||
  1002.                     (dir == BACKWARD && strchr(".!?)]\"'", c) != NULL && c != NUL))
  1003.             if (decl(&pos) == -1)
  1004.                 break;
  1005.  
  1006.         /* remember the line where the search started */
  1007.         startlnum = pos.lnum;
  1008.  
  1009.         for (;;)                /* find end of sentence */
  1010.         {
  1011.             if ((c = gchar(&pos)) == NUL ||
  1012.                             (pos.col == 0 && startPS(pos.lnum, NUL, FALSE)))
  1013.             {
  1014.                 if (dir == BACKWARD && pos.lnum != startlnum)
  1015.                     ++pos.lnum;
  1016.                 break;
  1017.             }
  1018.             if (c == '.' || c == '!' || c == '?')
  1019.             {
  1020.                 tpos = pos;
  1021.                 do
  1022.                     if ((c = inc(&tpos)) == -1)
  1023.                         break;
  1024.                 while (strchr(")}\"'", c = gchar(&tpos)) != NULL && c != NUL);
  1025.                 if (c == -1  || c == ' ' || c == '\t' || c == NUL)
  1026.                 {
  1027.                     pos = tpos;
  1028.                     if (gchar(&pos) == NUL) /* skip NUL at EOL */
  1029.                         inc(&pos);
  1030.                     break;
  1031.                 }
  1032.             }
  1033.             if ((*func)(&pos) == -1)
  1034.             {
  1035.                 if (count)
  1036.                     return FALSE;
  1037.                 noskip = TRUE;
  1038.                 break;
  1039.             }
  1040.         }
  1041. found:
  1042.             /* skip white space */
  1043.         while (!noskip && ((c = gchar(&pos)) == ' ' || c == '\t'))
  1044.             if (incl(&pos) == -1)
  1045.                 break;
  1046.     }
  1047.  
  1048.     setpcmark();
  1049.     curwin->w_cursor = pos;
  1050.     return TRUE;
  1051. }
  1052.  
  1053. /*
  1054.  * findpar(dir, count, what) - Find the next paragraph in direction 'dir'
  1055.  * Paragraphs are currently supposed to be separated by empty lines.
  1056.  * Return TRUE if the next paragraph was found.
  1057.  * If 'what' is '{' or '}' we go to the next section.
  1058.  * If 'both' is TRUE also stop at '}'.
  1059.  */
  1060.     int
  1061. findpar(dir, count, what, both)
  1062.     register int    dir;
  1063.     long            count;
  1064.     int             what;
  1065.     int                both;
  1066. {
  1067.     register linenr_t    curr;
  1068.     int                    did_skip;        /* TRUE after separating lines have
  1069.                                                 been skipped */
  1070.     int                    first;            /* TRUE on first line */
  1071.  
  1072.     curr = curwin->w_cursor.lnum;
  1073.  
  1074.     while (count--)
  1075.     {
  1076.         did_skip = FALSE;
  1077.         for (first = TRUE; ; first = FALSE)
  1078.         {
  1079.                 if (*ml_get(curr) != NUL)
  1080.                     did_skip = TRUE;
  1081.  
  1082.                 if (!first && did_skip && startPS(curr, what, both))
  1083.                     break;
  1084.  
  1085.                 if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count)
  1086.                 {
  1087.                         if (count)
  1088.                                 return FALSE;
  1089.                         curr -= dir;
  1090.                         break;
  1091.                 }
  1092.         }
  1093.     }
  1094.     setpcmark();
  1095.     if (both && *ml_get(curr) == '}')    /* include line with '}' */
  1096.         ++curr;
  1097.     curwin->w_cursor.lnum = curr;
  1098.     if (curr == curbuf->b_ml.ml_line_count)
  1099.     {
  1100.         if ((curwin->w_cursor.col = STRLEN(ml_get(curr))) != 0)
  1101.             --curwin->w_cursor.col;
  1102.         mincl = TRUE;
  1103.     }
  1104.     else
  1105.         curwin->w_cursor.col = 0;
  1106.     return TRUE;
  1107. }
  1108.  
  1109. /*
  1110.  * check if the string 's' is a nroff macro that is in option 'opt'
  1111.  */
  1112.     static int
  1113. inmacro(opt, s)
  1114.         char_u *opt;
  1115.         register char_u *s;
  1116. {
  1117.         register char_u *macro;
  1118.  
  1119.         for (macro = opt; macro[0]; ++macro)
  1120.         {
  1121.                 if (macro[0] == s[0] && (((s[1] == NUL || s[1] == ' ')
  1122.                         && (macro[1] == NUL || macro[1] == ' ')) || macro[1] == s[1]))
  1123.                         break;
  1124.                 ++macro;
  1125.                 if (macro[0] == NUL)
  1126.                         break;
  1127.         }
  1128.         return (macro[0] != NUL);
  1129. }
  1130.  
  1131. /*
  1132.  * startPS: return TRUE if line 'lnum' is the start of a section or paragraph.
  1133.  * If 'para' is '{' or '}' only check for sections.
  1134.  * If 'both' is TRUE also stop at '}'
  1135.  */
  1136.     int
  1137. startPS(lnum, para, both)
  1138.     linenr_t    lnum;
  1139.     int         para;
  1140.     int            both;
  1141. {
  1142.     register char_u *s;
  1143.  
  1144.     s = ml_get(lnum);
  1145.     if (*s == para || *s == '\f' || (both && *s == '}'))
  1146.         return TRUE;
  1147.     if (*s == '.' && (inmacro(p_sections, s + 1) || (!para && inmacro(p_para, s + 1))))
  1148.         return TRUE;
  1149.     return FALSE;
  1150. }
  1151.  
  1152. /*
  1153.  * The following routines do the word searches performed by the 'w', 'W',
  1154.  * 'b', 'B', 'e', and 'E' commands.
  1155.  */
  1156.  
  1157. /*
  1158.  * To perform these searches, characters are placed into one of three
  1159.  * classes, and transitions between classes determine word boundaries.
  1160.  *
  1161.  * The classes are:
  1162.  *
  1163.  * 0 - white space
  1164.  * 1 - letters, digits and underscore
  1165.  * 2 - everything else
  1166.  */
  1167.  
  1168. static int        stype;            /* type of the word motion being performed */
  1169.  
  1170. /*
  1171.  * cls() - returns the class of character at curwin->w_cursor
  1172.  *
  1173.  * The 'type' of the current search modifies the classes of characters if a 'W',
  1174.  * 'B', or 'E' motion is being done. In this case, chars. from class 2 are
  1175.  * reported as class 1 since only white space boundaries are of interest.
  1176.  */
  1177.     static int
  1178. cls()
  1179. {
  1180.     register int c;
  1181.  
  1182.     c = gchar_cursor();
  1183.     if (c == ' ' || c == '\t' || c == NUL)
  1184.         return 0;
  1185.  
  1186.     if (isidchar(c))
  1187.         return 1;
  1188.  
  1189.     /*
  1190.      * If stype is non-zero, report these as class 1.
  1191.      */
  1192.     return (stype == 0) ? 2 : 1;
  1193. }
  1194.  
  1195.  
  1196. /*
  1197.  * fwd_word(count, type, eol) - move forward one word
  1198.  *
  1199.  * Returns TRUE if the cursor was already at the end of the file.
  1200.  * If eol is TRUE, last word stops at end of line (for operators).
  1201.  */
  1202.     int
  1203. fwd_word(count, type, eol)
  1204.     long        count;
  1205.     int         type;
  1206.     int            eol;
  1207. {
  1208.     int         sclass;     /* starting class */
  1209.     int            i;
  1210.  
  1211.     stype = type;
  1212.     while (--count >= 0)
  1213.     {
  1214.         sclass = cls();
  1215.  
  1216.         /*
  1217.          * We always move at least one character.
  1218.          */
  1219.         i = inc_cursor();
  1220.         if (i == -1)
  1221.             return TRUE;
  1222.         if (i == 1 && eol && count == 0)    /* started at last char in line */
  1223.             return FALSE;
  1224.  
  1225.         if (sclass != 0)
  1226.             while (cls() == sclass)
  1227.             {
  1228.                 i = inc_cursor();
  1229.                 if (i == -1 || (i == 1 && eol && count == 0))
  1230.                     return FALSE;
  1231.             }
  1232.  
  1233.         /*
  1234.          * go to next non-white
  1235.          */
  1236.         while (cls() == 0)
  1237.         {
  1238.             /*
  1239.              * We'll stop if we land on a blank line
  1240.              */
  1241.             if (curwin->w_cursor.col == 0 && *ml_get(curwin->w_cursor.lnum) == NUL)
  1242.                 break;
  1243.  
  1244.             i = inc_cursor();
  1245.             if (i == -1 || (i == 1 && eol && count == 0))
  1246.                 return FALSE;
  1247.         }
  1248.     }
  1249.     return FALSE;
  1250. }
  1251.  
  1252. /*
  1253.  * bck_word(count, type) - move backward 'count' words
  1254.  *
  1255.  * Returns TRUE if top of the file was reached.
  1256.  */
  1257.     int
  1258. bck_word(count, type)
  1259.     long        count;
  1260.     int         type;
  1261. {
  1262.     int         sclass;     /* starting class */
  1263.  
  1264.     stype = type;
  1265.     while (--count >= 0)
  1266.     {
  1267.         sclass = cls();
  1268.  
  1269.         if (dec_cursor() == -1)        /* started at start of file */
  1270.             return TRUE;
  1271.  
  1272.         if (cls() != sclass || sclass == 0)
  1273.         {
  1274.             /*
  1275.              * We were at the start of a word. Go back to the end of the prior
  1276.              * word.
  1277.              */
  1278.             while (cls() == 0)  /* skip any white space */
  1279.             {
  1280.                 /*
  1281.                  * We'll stop if we land on a blank line
  1282.                  */
  1283.                 if (curwin->w_cursor.col == 0 && *ml_get(curwin->w_cursor.lnum) == NUL)
  1284.                     goto finished;
  1285.  
  1286.                 if (dec_cursor() == -1)        /* hit start of file, stop here */
  1287.                     return FALSE;
  1288.             }
  1289.             sclass = cls();
  1290.         }
  1291.  
  1292.         /*
  1293.          * Move backward to start of this word.
  1294.          */
  1295.         if (skip_chars(sclass, BACKWARD))
  1296.                 return FALSE;
  1297.  
  1298.         inc_cursor();                    /* overshot - forward one */
  1299. finished:
  1300.         ;
  1301.     }
  1302.     return FALSE;
  1303. }
  1304.  
  1305. /*
  1306.  * end_word(count, type, stop) - move to the end of the word
  1307.  *
  1308.  * There is an apparent bug in the 'e' motion of the real vi. At least on the
  1309.  * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e'
  1310.  * motion crosses blank lines. When the real vi crosses a blank line in an
  1311.  * 'e' motion, the cursor is placed on the FIRST character of the next
  1312.  * non-blank line. The 'E' command, however, works correctly. Since this
  1313.  * appears to be a bug, I have not duplicated it here.
  1314.  *
  1315.  * Returns TRUE if end of the file was reached.
  1316.  *
  1317.  * If stop is TRUE and we are already on the end of a word, move one less.
  1318.  */
  1319.     int
  1320. end_word(count, type, stop)
  1321.     long        count;
  1322.     int         type;
  1323.     int            stop;
  1324. {
  1325.     int         sclass;     /* starting class */
  1326.  
  1327.     stype = type;
  1328.     while (--count >= 0)
  1329.     {
  1330.         sclass = cls();
  1331.         if (inc_cursor() == -1)
  1332.             return TRUE;
  1333.  
  1334.         /*
  1335.          * If we're in the middle of a word, we just have to move to the end of it.
  1336.          */
  1337.         if (cls() == sclass && sclass != 0)
  1338.         {
  1339.             /*
  1340.              * Move forward to end of the current word
  1341.              */
  1342.             if (skip_chars(sclass, FORWARD))
  1343.                     return TRUE;
  1344.         }
  1345.         else if (!stop || sclass == 0)
  1346.         {
  1347.             /*
  1348.              * We were at the end of a word. Go to the end of the next word.
  1349.              */
  1350.  
  1351.             if (skip_chars(0, FORWARD))     /* skip any white space */
  1352.                 return TRUE;
  1353.  
  1354.             /*
  1355.              * Move forward to the end of this word.
  1356.              */
  1357.             if (skip_chars(cls(), FORWARD))
  1358.                 return TRUE;
  1359.         }
  1360.         dec_cursor();                    /* overshot - backward one */
  1361.         stop = FALSE;                    /* we move only one word less */
  1362.     }
  1363.     return FALSE;
  1364. }
  1365.  
  1366.     int
  1367. skip_chars(class, dir)
  1368.     int class;
  1369.     int dir;
  1370. {
  1371.         while (cls() == class)
  1372.             if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1)
  1373.                 return TRUE;
  1374.         return FALSE;
  1375. }
  1376.